function Y = slpcafea(S, X, cri, param)
%SLPCAFEA Extracts the PCA feature for samples
%
% [ Syntax ]
%   - Y = slpcafea(S, X);
%   - Y = slpcafea(S, X, 'num', k);
%   - Y = slpcafea(S, X, 'energy', er);
%   - Y = slpcafea(S, X, 'select', inds);
%   - f = slpcafea(S, [], ...);
%
% [ Arguments ]
%   - S:        The struct of the trained PCA model
%   - X:        The sample matrix (d0 x n)
%   - k:        The number of leading eigenvalues
%   - er:       The energy preservation ratio
%   - inds:     The indices of the selected eigenvalues
%   - Y:        The feature matrix (df x n)
%   - f:        The feature extraction function handle.
%
% [ Description ]
%   - Y = slpcafea(S, X) extracts all PCA features corresponding the whole 
%     subspace preserved by the PCA model. 
%
%   - Y = slpcafea(S, X, 'num', k) extracts the PCA features corresponding to
%     the k leading subspace dimensions.
%
%   - Y = slpcafea(S, X, 'energy', er) extracts the PCA features
%     corresponding to leading subspace dimensions such that the ratio of 
%     energy preserved in the features is at least er. 
%
%     Please note that here er specifies the ratio of the feature energy to
%     the original total energy. If the energy preserved in the PCA model S
%     is less than er, then the features corresponding to the whole
%     preserved subspace will be extracted.
%
%   - Y = slpcafea(S, X, 'select', inds) extracts the selected PCA
%     features. Here, inds can be integer indices or a logical array.
%
%   - f = slpcafea(S, [], ...) returns the function handle f for feature
%     extraction, which can be invoked using the syntax Y = f(X).
%
% [ Remarks ]
%   - It is recommended that you extract features for a batch of multiple
%     samples in each invoking. When there are multiple batches, it is
%     recommended to first get the extraction function handle f using the
%     syntax f = slpcafea(S, [], ...) and then apply f to each batch.
%     Thereby, the overhead can be considerably reduced.
%
% [ History ]
%   - Created by Dahua Lin, on Jul 17, 2007
%


%% parse and verify input arguments

error(nargchk(2, 4, nargin));

% S and X
assert(isstruct(S), 'sltoolbox:slpcafea:invalidarg', ...
    'S should be a struct representing the PCA model.');

assert(isempty(X) || (isfloat(X) && ndims(X) == 2), 'sltoolbox:slpcafea:invalidarg', ...
    'X should be empty or a 2D numeric matrix.');

% cri
if nargin < 3
    cri = [];
else
    assert(ischar(cri), 'sltoolbox:slpcafea:invalidarg', ...
        'The 3rd argument to slpcafea should be a string indicating the criteria for feature selection.');
    
    assert(nargin >= 4, 'sltoolbox:slpcafea:invalidarg', ...
        'slpcafea lacks the parameter for feature selection criteria %s.', cri);    
end

%% main

% get projection matrix
if isempty(cri)
    P = S.P;
else
    switch cri
        case 'num'
            assert(isnumeric(param) && isscalar(param) && param > 0 && param == fix(param), ...
                'sltoolbox:slpcafea:invalidparam', ...
                'The number of features should be a positive integer scalar.');
            assert(param <= S.feadim, 'sltoolbox:slpcafea:invalidparam', ...
                'The number of features to be extracted should not exceed the preserved number.');
            
            if (param < S.feadim)
                P = S.P(:, 1:param);
            else
                P = S.P;
            end
            
        case 'energy'
            assert(isnumeric(param) && isscalar(param) && param > 0 && param <= 1, ...
                'sltoolbox:slpcafea:invalidparam', ...
                'The energy ratio should be a scalar in range (0, 1]');
            
            if (param >= S.ratio)
                P = S.P;
            else
                k = find(cumsum(S.eigvals) >= S.total * param, 1);
                if ~isempty(k)
                    P = S.P(:, 1:k);
                else
                    P = S.P;
                end
            end
            
        case 'select'
            P = S.P(:, param);
            
        otherwise
            error('sltoolbox:slpcafea:unknowncri', ...
                'Unknown feature selection criteria %s', cri);
    end
end

% do extraction

Pt = P';

if ~isempty(X)
    if all(S.vmean == 0)
        Y = Pt * X;
    else
        Y = Pt * bsxfun(@minus, X, S.vmean);
    end
else
    if all(S.vmean == 0)
        Y = @(x) Pt * x;
    else
        Y = @(x) Pt * bsxfun(@minus, x, S.vmean);
    end
end

